-- 设置
local KEY = GetModConfigData("sw_myautocook")
local search_range = 6*4

local e_util = require "libs/entutil"
local p_util = require "libs/playerutil"
local t_util = require "libs/tableutil"

local id_thread = "HUXI_AUTO_COOK"
local thread


local mode = "烹饪模式"
local Stewer = {
    prefab = "cookpot",
    name = e_util:GetPrefabName("cookpot"),
}
local Ings = {}
local StewerFns = {}
local SearchFood = ""

local function TIPS(color, content)
    TIP("自动烹饪", color, content, "head")
end
local Tags_Stewer = {"structure", "_container", "stewer"}
local stewer_prefabs = {"alchmy_fur"}

local function IsStewer(ent)
    return e_util:IsValid(ent) and StewerFns[ent.prefab] 
    and (ent:HasTags(Tags_Stewer) or table.contains(stewer_prefabs, ent.prefab))
    and e_util:GetContainer(ent) 
end

-- 记录烹饪数据
local function LoadStewerData(ent)
    local cont = IsStewer(ent)
    if not cont then return end
    local s_prefab = ent.prefab
    local s_name = e_util:GetPrefabName(s_prefab, ent)
    local tmp_ings = {}
    if cont and cont:IsFull() then
        local items = cont:GetItems()
        for _, item in pairs(items)do
            local atlas, image = e_util:GetAtlasAndImage(item)
            if item.prefab and atlas and image and item.name then
                -- print(item.name, atlas, image)
                table.insert(tmp_ings, {
                    prefab = item.prefab,
                    name = item.name,
                    atlas = atlas,
                    image = image,
                })
            else
                TIPS("red", "错误！无法获取食材属性")
            end
        end
    end
    if #tmp_ings ~= 0 and s_prefab and s_name then
        Stewer.prefab = s_prefab
        Stewer.name = s_name
        Ings = tmp_ings
    end
end

local function Banit(mystew, time)
    mystew.hx_ban = true
    mystew:DoTaskInTime(time or 1, function(inst) inst.hx_ban=nil end)
end

-- 开烹
local function Cookit(mystew, time)
    local fn = StewerFns[mystew.prefab]
    if fn then fn(mystew, GLOBAL.ThePlayer) end
    Banit(mystew, time)
end

-- 玩家烹饪并记录当前打开的锅
local function PlayerCookIt()
    local windows = GLOBAL.ThePlayer and GLOBAL.ThePlayer.HUD.controls.containers or {}
    local ent = t_util:GetElement(windows, function(ent, widget)
        return IsStewer(ent) and widget and widget.button and widget.button:IsEnabled() and ent
    end)
    if ent then
        LoadStewerData(ent)
        Cookit(ent)
    end
end

-- 是否有足够的食材，有的话会自动标记好食材的位置
local function HaveEnoughIngs(onlyplayer)
    t_util:Pairs(Ings, function(_, food)
        food.cont = nil
    end)
    local invent = GLOBAL.ThePlayer and GLOBAL.ThePlayer.replica.inventory
    if not invent then return end

    local function mark_cont_inst(cont_inst)
        local cont = e_util:GetContainer(cont_inst)
        if cont then
            -- 一定要注意not优先级最高
            if onlyplayer and (not (cont_inst==GLOBAL.ThePlayer)) and not cont_inst:HasTag("INLIMBO") then
                return
            end
            t_util:Pairs(cont:GetItems(), function(slot, item)
                local num = e_util:GetStacksize(item)
                t_util:Pairs(Ings, function(_, food)
                    if item and food and item.prefab == food.prefab and not food.cont and num > 0 then
                        food.cont = cont_inst
                        food.slot = slot
                        num = num - 1
                    end
                end)
            end)
        end
    end
    -- 先放身上的再放容器的
    mark_cont_inst(GLOBAL.ThePlayer)
    t_util:Pairs(invent:GetOpenContainers(), function(cont_inst)
        if not IsStewer(cont_inst) then mark_cont_inst(cont_inst) end
    end)
    if t_util:GetElement(Ings, function(_, food) 
        if not food.cont then
            SearchFood = food.prefab
            return true
        end
    end) then
        return false
    end
    return Ings
end



local function IsCook_ing_ed(ent)
    -- 不用tag或者动画检查的原因是为了适配所有的"锅"内容器，包括模组的锅
    local ids = p_util:GetActionIDs(ent) or {}
    if table.contains(ids, "HARVEST") then
        return true
    elseif table.contains(ids, "RUMMAGE") then
        return false
    end
    local anim = e_util:GetAnim(ent) or ""
    return (not anim:match("pre")) and (anim:match("loop") or anim:match("pst"))
end

-- 被别人打开了
local function IsOpenByFri(ent)
    local anim = e_util:GetAnim(ent) or ""
    return (anim:match("open") or anim:match("pre")) and e_util:FindEnt(ent, nil, 3, {"player"}, {'FX','DECOR','INLIMBO','NOCLICK'}, nil, nil, function(ent)
        return ent ~= GLOBAL.ThePlayer
    end)
end

local function GetUI()
    local menu = GLOBAL.ThePlayer and GLOBAL.ThePlayer.HUD and GLOBAL.ThePlayer.HUD.controls and GLOBAL.ThePlayer.HUD.controls.craftingmenu
    return menu and menu.pinbar and menu.pinbar.open_menu_button and menu.pinbar.open_menu_button.icon
end
local MV = false
local function StopThread(message)
    GLOBAL.KillThreadsWithID(id_thread)
    if thread then
        TIPS("white", message or "烹饪结束")
    end
    thread = nil
    if MV then GLOBAL.ThePlayer:EnableMovementPrediction(true) end
    local icon = GetUI()
    if icon and icon.acui then
        icon.acui:Kill()
    end
end
local Widget = require "widgets/widget"
local Image = require "widgets/image"
local function ShowUI()
    local icon = GetUI()
    if not icon then return end
    if icon.acui then
        icon.acui:Kill()
    end
    local ox,oy = icon:GetSize()
    icon.acui = icon:AddChild(Widget("autocook"))
    icon.acui:SetPosition(2*ox, 0)
    local space = 5
    local width = ox+space
    t_util:Pairs(Ings, function(slot, food)
        local sui = icon.acui:AddChild(Widget("autocook_slot"..slot))
        sui:SetPosition((slot-1)*(ox + space*2), 0)
        -- sui.bg = sui:AddChild(Image("images/hud.xml", "inv_slot.tex"))
        sui.bg = sui:AddChild(Image("images/quagmire_recipebook.xml", "cookbook_known.tex"))
        sui.bg:SetSize(width, width)
        sui.img = sui:AddChild(Image(food.atlas, food.image))
        sui.img:SetSize(width, width)
    end)
end

local function StartAutoCook(ent)
    if KEY == "middle" then
        mode = IsCook_ing_ed(ent) and "收取模式" or "烹饪模式"
    else
        mode = GLOBAL.TheInput:IsKeyDown(GLOBAL.KEY_CTRL) and "收取模式" or "烹饪模式"
    end
    PlayerCookIt()
    ShowUI()
    TIPS("green", mode)
    if thread then return end
    thread = GLOBAL.StartThread(function()
        MV = GLOBAL.Profile and GLOBAL.Profile:GetMovementPredictionEnabled()
        if MV then GLOBAL.ThePlayer:EnableMovementPrediction(false) end
        while thread do
            repeat GLOBAL.Sleep(GLOBAL.FRAMES*3) until not p_util:IsInBusy()
            if p_util:GetActiveItem() then return StopThread("满啦满啦，拿不下啦") end
            if mode == "收取模式" then
                local dontstop = e_util:FindEnt(nil, nil, search_range, Tags_Stewer, nil, nil, {"burnt"}, function(ent)
                    return IsCook_ing_ed(ent)
                end)
                if dontstop then
                    local thestewer = e_util:FindEnt(nil, nil, search_range, Tags_Stewer, nil, nil, nil, function(ent)
                        return table.contains(p_util:GetActionIDs(ent) or {}, "HARVEST")
                    end)
                    if thestewer then
                        p_util:ActClick(thestewer, "HARVEST", true)
                    end
                else
                    GLOBAL.TheFocalPoint.SoundEmitter:PlaySound("dontstarve/HUD/research_available")
                    return StopThread("收取结束") 
                end
            else
                if #Ings == 0 then return StopThread("不知道吃啥，请放置好食材 或先 烹饪一锅") end
                if not e_util:FindEnt(nil, Stewer.prefab, search_range, Tags_Stewer) then return StopThread("附近没锅") end
                if mode == "烹饪模式" then
                    -- 有足够料理
                    if HaveEnoughIngs() then
                        local windows = GLOBAL.ThePlayer and GLOBAL.ThePlayer.HUD.controls.containers or {}
                        local mystew = t_util:GetElement(windows, function(ent, widget) return IsStewer(ent) and not ent.hx_ban and ent end)
                        local cont = e_util:GetContainer(mystew)
                        if cont then
                            -- 有打开的锅
                            if cont:IsEmpty() then
                                -- local ping = TheNet:GetAveragePing()
                                -- if ping < 40 then
                                    t_util:Pairs(Ings, function(_, food) 
                                        -- food.cont:MoveItemFromAllOfSlot(food.slot, mystew) 官方默认的调用方法似乎有时间补偿机制，不能极速
                                        if food.cont == GLOBAL.ThePlayer then
                                            GLOBAL.SendRPCToServer(GLOBAL.RPC.MoveInvItemFromAllOfSlot, food.slot, mystew)
                                        else
                                            GLOBAL.SendRPCToServer(GLOBAL.RPC.MoveItemFromAllOfSlot, food.slot, food.cont, mystew)
                                        end
                                    end)
                                    if GLOBAL.TheWorld and GLOBAL.TheWorld.ismastersim then
                                        repeat GLOBAL.Sleep(GLOBAL.FRAMES*3) until cont:IsFull()
                                    end
                                    repeat Cookit(mystew) GLOBAL.Sleep(GLOBAL.FRAMES) until not t_util:GetElement(GLOBAL.ThePlayer and GLOBAL.ThePlayer.HUD.controls.containers or {}, function(ent, widget) return IsStewer(ent) and not ent.hx_ban and ent end)
                                    -- Cookit(mystew)
                                -- else
                                --     t_util:Pairs(Ings, function(slot, food)
                                --         if food.cont == GLOBAL.ThePlayer then
                                --             GLOBAL.SendRPCToServer(GLOBAL.RPC.MoveInvItemFromAllOfSlot, food.slot, mystew)
                                --         else
                                --             GLOBAL.SendRPCToServer(GLOBAL.RPC.MoveItemFromAllOfSlot, food.slot, food.cont, mystew)
                                --         end
                                --         repeat GLOBAL.Sleep(GLOBAL.FRAMES*3) until cont:GetItemInSlot(slot)
                                --     end)
                                --     repeat Cookit(mystew) until cont:IsFull() and not t_util:GetElement(GLOBAL.ThePlayer and GLOBAL.ThePlayer.HUD.controls.containers or {}, function(ent, widget) return IsStewer(ent) and not ent.hx_ban and ent end)
                                -- end
                            else
                                -- 锅里有东西了
                                -- print("锅里有东西")
                                local items = cont:GetItems()
                                if t_util:GetElement(Ings, function(slot, food)
                                    local pr = items and items[slot] and items[slot].prefab
                                    return pr ~= food.prefab
                                end) then
                                    if IsOpenByFri(mystew) then
                                        TIPS("red", "这口锅被占用")
                                        Banit(mystew, 15)
                                    else
                                        -- print("尝试拿取")
                                        if t_util:GetElement(items, function(slot, food)
                                            if p_util:CanTakeItem(food) then
                                                GLOBAL.SendRPCToServer(GLOBAL.RPC.MoveItemFromAllOfSlot, slot, mystew, GLOBAL.ThePlayer)
                                            else
                                                return true
                                            end
                                        end) then
                                            TIPS("yellow", "物品栏已满, 转收取模式")
                                            mode = "收取模式"
                                        end
                                        Banit(mystew)
                                    end
                                else
                                    -- 烹饪锅满足
                                    -- print("满足烹饪")
                                    repeat Cookit(mystew) until cont:IsFull() and not t_util:GetElement(GLOBAL.ThePlayer and GLOBAL.ThePlayer.HUD.controls.containers or {}, function(ent, widget) return IsStewer(ent) and not ent.hx_ban and ent end)
                                end
                            end
                        else
                            mystew = e_util:FindEnt(nil, Stewer.prefab, search_range, Tags_Stewer, {"burnt"}, nil, nil, function(ent)
                                if not IsOpenByFri(ent) then
                                    local ids = p_util:GetActionIDs(ent)
                                    return table.contains(ids, "RUMMAGE") or table.contains(ids, "HARVEST")
                                end
                            end)
                            if mystew then
                                p_util:TryClick(mystew, {"RUMMAGE", "HARVEST"})
                            end
                        end
                    else
                        mode = "准备模式"
                        TIPS("pink", "寻找食材 "..e_util:GetPrefabName(SearchFood))
                    end
                elseif mode == "准备模式" then
                    if HaveEnoughIngs(true) then
                        mode = "烹饪模式"
                    else
                        local needname = e_util:GetPrefabName(SearchFood)
                        local food = e_util:FindEnt(nil, SearchFood, search_range, nil, nil, nil, nil, function(ent)
                            return e_util:InValidPos(ent) and table.contains(p_util:GetActionIDs(ent), "PICKUP")
                        end)
                        -- 地上有就捡起来
                        if food then
                            GLOBAL.ThePlayer:DoTaskInTime(0, function()
                                p_util:TryClick(food, "PICKUP")
                            end)
                            TIPS("white", "捡起 "..e_util:GetPrefabName(SearchFood, food).."，洗洗还能吃")
                        else
                            local searfn = GLOBAL.TheWorld and not GLOBAL.TheWorld.ismastersim and GLOBAL.MOD_ShroomCake.SearchContainerWithPrefab
                            if type(searfn) == "function" then
                                local cont_inst = searfn(SearchFood)
                                if cont_inst then
                                    -- 没打开有材料的箱子
                                    local cont_open = p_util:IsOpenContainer(cont_inst)
                                    if cont_open then
                                        -- 有物品并拿取
                                        if t_util:GetElement(cont_open:GetItems(), function(slot, item)
                                            if item and item.prefab == SearchFood then
                                                if p_util:CanTakeItem(item) then
                                                    GLOBAL.SendRPCToServer(GLOBAL.RPC.MoveItemFromAllOfSlot, slot, cont_inst, GLOBAL.ThePlayer)
                                                    TIPS("white", "拿走 "..e_util:GetPrefabName(SearchFood, item))
                                                else
                                                    mode = "收取模式"
                                                    TIPS("white", "食材 "..e_util:GetPrefabName(SearchFood, item).." 拿不下了")
                                                end
                                                return true
                                            end
                                        end) then
                                            -- 要换模式吗
                                        else
                                            -- 没有食材关箱子
                                            p_util:TryClick(cont_inst, "RUMMAGE")
                                        end
                                    else
                                        p_util:TryClick(cont_inst, "RUMMAGE")
                                    end
                                else
                                    mode = "收取模式"
                                    TIPS("pink", needname.." 已经用完了...")
                                end
                            else
                                mode = "收取模式"
                                TIPS("pink", "捡不到 "..needname)
                            end
                        end
                    end
                end
            end
        end
        StopThread()
    end, id_thread)
end
InterruptedByMobile(function() return thread end, StopThread)
AddClassPostConstruct("screens/playerhud", function(self)
	local __OpenContainer = self.OpenContainer
	self.OpenContainer = function(HUD, ent, ...)
		local sult = __OpenContainer(HUD, ent, ...)
        if type(ent)=="table" and ent.prefab and StewerFns[ent.prefab] then return sult end
		local cont = e_util:GetContainer(ent)
		if cont then
			local widget = cont:GetWidget()
			local _fn = widget and widget.buttoninfo and widget.buttoninfo.fn
            if _fn then
                StewerFns[ent.prefab] = _fn
                widget.buttoninfo.fn = function(ent, ...)
                    LoadStewerData(ent)
                    return _fn(ent, ...)
                end
            end
		end
		return sult
	end
end)
if KEY == "middle" then
    GLOBAL.TheInput:AddMouseButtonHandler(function(button, down, x, y)
        if button == GLOBAL.MOUSEBUTTON_MIDDLE 
        and not down 
        and e_util:InGame()
        then
            local ent = GLOBAL.TheInput:GetWorldEntityUnderMouse()
            if IsStewer(ent) then
                StartAutoCook(ent)
            end
        end
    end)
else
    AddBindBtn("sw_myautocook", StartAutoCook)
end